#include "storage/disk_manager.h"
#include "storage/page.h"
#include "storage/buffer_pool.h"
#include <cassert>
#include <cstring>
#include <iostream>
#include <string>
#include <memory>

void testBasicOperations() {
    std::cout << "Testing BufferPool basic operations...\n";
    
    std::string filename = "test_buffer_pool.db";
    std::remove(filename.c_str());
    
    // Create DiskManager and BufferPool
    std::unique_ptr<DiskManager> diskManager = std::make_unique<DiskManager>(filename);
    BufferPool bufferPool(10, std::move(diskManager));
    
    // Test newPage functionality
    Page* page1 = bufferPool.newPage();
    uint32_t pageId1 = page1->getPageId();
    assert(pageId1 == 0);
    
    Page* page2 = bufferPool.newPage();
    uint32_t pageId2 = page2->getPageId();
    assert(pageId2 > 0 && pageId2 != pageId1);
    
    // Test getPage functionality (page already in buffer)
    Page* retrievedPage1 = bufferPool.getPage(pageId1);
    assert(retrievedPage1 == page1);
    
    // Verify hit count increased
    assert(bufferPool.getHitCount() == 1);
    
    // Modify page data and mark as dirty
    char* data = page1->getData().data();
    for (int i = 0; i < 100; ++i) {
        data[i] = static_cast<char>(i + 100);
    }
    page1->setDirty(true);
    
    // Flush page to disk
    bufferPool.flushPage(pageId1);
    assert(!page1->isDirty());
    
    // Create a new DiskManager to verify data was written correctly to disk
    std::unique_ptr<DiskManager> verifyDiskManager = std::make_unique<DiskManager>(filename);
    char verifyData[PAGE_SIZE] = {0};
    verifyDiskManager->readPage(pageId1, verifyData);
    
    for (int i = 0; i < 100; ++i) {
        assert(verifyData[i] == static_cast<char>(i + 100));
    }
    
    std::cout << "Basic operations test passed!\n";
    std::remove(filename.c_str());
}

void testLRUReplacement() {
    std::cout << "Testing BufferPool LRU replacement...\n";
    
    std::string filename = "test_buffer_pool_lru.db";
    std::remove(filename.c_str());
    
    // Create BufferPool with capacity 3 for testing LRU replacement
    std::unique_ptr<DiskManager> diskManager = std::make_unique<DiskManager>(filename);
    BufferPool bufferPool(3, std::move(diskManager));
    
    // Create 3 pages (fill the buffer)
    Page* page1 = bufferPool.newPage();
    uint32_t pageId1 = page1->getPageId();
    assert(pageId1 == 0);
    
    Page* page2 = bufferPool.newPage();
    uint32_t pageId2 = page2->getPageId();
    assert(pageId2 == 1);
    
    Page* page3 = bufferPool.newPage();
    uint32_t pageId3 = page3->getPageId();
    assert(pageId3 == 2);
    
    // Save the original data of page 3
    char originalData3[PAGE_SIZE];
    std::memcpy(originalData3, page3->getData().data(), PAGE_SIZE);
    
    // Modify page 1 data and flush to disk, then modify page 2 data
    char* data1 = page1->getData().data();
    for (int i = 0; i < 100; ++i) {
        data1[i] = static_cast<char>(i + 200);
    }
    page1->setDirty(true);
    bufferPool.flushPage(pageId1);
    
    char* data2 = page2->getData().data();
    for (int i = 0; i < 100; ++i) {
        data2[i] = static_cast<char>(i + 300);
    }
    page2->setDirty(true);
    
    // Access page 1, making page 3 the last in LRU list
    bufferPool.getPage(pageId1);
    
    // Create 4th page, which should cause page 3 to be evicted
    Page* page4 = bufferPool.newPage();
    uint32_t pageId4 = page4->getPageId();
    
    // Verify page 2 is still in buffer (since it's dirty)
    Page* retrievedPage2 = bufferPool.getPage(pageId2);
    assert(retrievedPage2->isDirty());
    
    // Verify page 3 data was written correctly to disk
    std::unique_ptr<DiskManager> verifyDiskManager = std::make_unique<DiskManager>(filename);
    char verifyData3[PAGE_SIZE] = {0};
    verifyDiskManager->readPage(pageId3, verifyData3);
    
    // Page 3 wasn't modified, so data should match the original data
    bool dataMatches = true;
    for (int i = 0; i < PAGE_SIZE; ++i) {
        if (verifyData3[i] != originalData3[i]) {
            dataMatches = false;
            break;
        }
    }
    assert(dataMatches);
    
    std::cout << "LRU replacement test passed!\n";
    std::remove(filename.c_str());
}

void testFlushAll() {
    std::cout << "Testing BufferPool flushAll...\n";
    
    std::string filename = "test_buffer_pool_flushall.db";
    std::remove(filename.c_str());
    
    std::unique_ptr<DiskManager> diskManager = std::make_unique<DiskManager>(filename);
    BufferPool bufferPool(5, std::move(diskManager));
    
    // Create multiple dirty pages
    const int numPages = 4;
    std::vector<Page*> pages;
    std::vector<uint32_t> pageIds;
    
    for (int i = 0; i < numPages; ++i) {
        Page* page = bufferPool.newPage();
        pages.push_back(page);
        pageIds.push_back(page->getPageId());
        
        // Modify page data
        char* data = page->getData().data();
        for (int j = 0; j < 100; ++j) {
            data[j] = static_cast<char>(i * 100 + j);
        }
        page->setDirty(true);
    }
    
    // Flush all pages
    bufferPool.flushAll();
    
    // Verify all pages are no longer dirty
    for (Page* page : pages) {
        assert(!page->isDirty());
    }
    
    // Create a new DiskManager to verify all data was written correctly to disk
    std::unique_ptr<DiskManager> verifyDiskManager = std::make_unique<DiskManager>(filename);
    
    for (int i = 0; i < numPages; ++i) {
        char verifyData[PAGE_SIZE] = {0};
        verifyDiskManager->readPage(pageIds[i], verifyData);
        
        for (int j = 0; j < 100; ++j) {
            assert(verifyData[j] == static_cast<char>(i * 100 + j));
        }
    }
    
    std::cout << "flushAll test passed!\n";
    std::remove(filename.c_str());
}

void testHitAndMissCount() {
    std::cout << "Testing BufferPool hit and miss count...\n";
    
    std::string filename = "test_buffer_pool_counts.db";
    std::remove(filename.c_str());
    
    std::unique_ptr<DiskManager> diskManager = std::make_unique<DiskManager>(filename);
    BufferPool bufferPool(3, std::move(diskManager));
    
    // Initial hit and miss counts should be 0
    assert(bufferPool.getHitCount() == 0);
    assert(bufferPool.getMissCount() == 0);
    
    // Create 3 pages - note: newPage() doesn't increment missCount
    Page* page1 = bufferPool.newPage();
    Page* page2 = bufferPool.newPage();
    Page* page3 = bufferPool.newPage();
    
    // missCount should still be 0 after newPage()
    assert(bufferPool.getMissCount() == 0);
    
    // Retrieve these pages again, these should all be hits
    bufferPool.getPage(page1->getPageId());
    bufferPool.getPage(page2->getPageId());
    bufferPool.getPage(page3->getPageId());
    
    assert(bufferPool.getHitCount() == 3);
    
    std::cout << "Hit and miss count test passed!\n";
    std::remove(filename.c_str());
}

int main() {
    std::cout << "Running BufferPool unit tests...\n\n";
    
    testBasicOperations();
    std::cout << "\n";
    
    testLRUReplacement();
    std::cout << "\n";
    
    testFlushAll();
    std::cout << "\n";
    
    testHitAndMissCount();
    
    std::cout << "\nAll BufferPool unit tests passed!\n";
    return 0;
}